<?php

namespace App\Services\Cloud\VeTos;

use Illuminate\Support\Arr;
use League\Flysystem\Config;
use League\Flysystem\FileAttributes;
use League\Flysystem\FilesystemAdapter;
use League\Flysystem\UnableToWriteFile;
use Tos\Exception\TosClientException;
use Tos\Exception\TosServerException;
use Tos\Model\CopyObjectInput;
use Tos\Model\DeleteObjectInput;
use Tos\Model\Enum;
use Tos\Model\GetObjectInput;
use Tos\Model\HeadObjectInput;
use Tos\Model\PreSignedURLInput;
use Tos\Model\PutObjectACLInput;
use Tos\Model\PutObjectInput;
use Tos\TosClient;

class TosAdapter implements FilesystemAdapter
{
    protected $client = null;

    public function __construct(protected string $accessKey, protected string $secretKey, protected string $bucket, protected string $domain, protected string $region = 'cn-beijing')
    {
        $this->client = new TosClient([
            'region' => $this->region,
            'endpoint' => $this->domain,
            'ak' => $this->accessKey,
            'sk' => $this->secretKey,
        ]);
    }

    /**
     * 判断文件是否存在
     *
     * @param  string $path
     * @return bool
     *                -etag: ""13a80e166ae5e7a53d0947a0be743ac3""
     *                -lastModified: 1692423310
     *                -deleteMarker: false
     *                -ssecAlgorithm: ""
     *                -ssecKeyMD5: ""
     *                -versionId: ""
     *                -websiteRedirectLocation: ""
     *                -objectType: ""
     *                -hashCrc64ecma: "3915531081372297637"
     *                -storageClass: "STANDARD"
     *                -meta: []
     *                -contentLength: 12182
     *                -cacheControl: ""
     *                -contentDisposition: ""
     *                -contentEncoding: ""
     *                -contentLanguage: ""
     *                -contentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
     */
    public function fileExists(string $path): bool
    {
        try {
            $output = $this->client->headObject(new HeadObjectInput($this->bucket, $path));
        } catch (TosServerException $e) {
            logger()->warning(__METHOD__, ['e' => $e]);

            return false;
        }

        return $output->getContentLength() > 0;
    }

    public function directoryExists(string $path): bool
    {
        // TODO: Implement directoryExists() method.
    }

    public function write(string $path, string $contents, Config $config): void
    {
        try {
            //        $mime = $config->get('mime', 'application/octet-stream');
            $acl = $config->get('acl');
            $storageClass = $config->get('storageClass', Enum::StorageClassStandard);

            $input = new PutObjectInput($this->bucket, $path, $contents);
            if ($acl) {
                $input->setACL($acl);
            }
            $input->setStorageClass($storageClass);
            $this->client->putObject($input);
        } catch (TosClientException|TosServerException $exception) {
            throw UnableToWriteFile::atLocation($path, $exception->getMessage());
        }
    }

    public function writeStream(string $path, $contents, Config $config): void
    {
        $file = is_resource($contents) ? $contents : fopen($contents, 'r');
        try {
            $acl = $config->get('acl');
            $storageClass = $config->get('storageClass', Enum::StorageClassStandard);
            $input = new PutObjectInput($this->bucket, $path, $file);
            if ($acl) {
                $input->setACL($acl);
            }
            $input->setStorageClass($storageClass);
            $this->client->putObject($input);
        } catch (TosClientException|TosServerException $exception) {
            throw UnableToWriteFile::atLocation($path, $exception->getMessage());
        } finally {
            if (is_resource($file)) {
                fclose($file);
            }
        }
    }

    public function read(string $path): string
    {
        try {
            $input = new GetObjectInput($this->bucket, $path);
            $output = $this->client->getObject($input);

            return $output->getContent()->getContents();
        } catch (TosClientException|TosServerException $exception) {
            throw UnableToWriteFile::atLocation($path, $exception->getMessage());
        }
    }

    public function readStream(string $path)
    {
        // TODO: Implement readStream() method.
    }

    public function delete(string $path): void
    {
        try {
            $input = new DeleteObjectInput($this->bucket, $path);
            $this->client->deleteObject($input);
        } catch (TosClientException|TosServerException $exception) {
            throw UnableToWriteFile::atLocation($path, $exception->getMessage());
        }
    }

    public function deleteDirectory(string $path): void
    {
        // TODO: Implement deleteDirectory() method.
    }

    public function createDirectory(string $path, Config $config): void
    {
        // TODO: Implement createDirectory() method.
    }

    public function setVisibility(string $path, string $visibility): void
    {
        try {
            $input = new PutObjectACLInput($this->bucket, $path);
            $input->setACL($visibility);
            $this->client->putObjectACL($input);
        } catch (TosClientException|TosServerException $exception) {
            throw UnableToWriteFile::atLocation($path, $exception->getMessage());
        }
    }

    public function visibility(string $path): FileAttributes
    {
        // TODO: Implement visibility() method.
    }

    public function mimeType(string $path): FileAttributes
    {
    }

    public function lastModified(string $path): FileAttributes
    {
        // TODO: Implement lastModified() method.
    }

    public function fileSize(string $path): FileAttributes
    {
        // TODO: Implement fileSize() method.
    }

    public function listContents(string $path, bool $deep): iterable
    {
        // TODO: Implement listContents() method.
    }

    public function move(string $source, string $destination, Config $config): void
    {
        // TODO: Implement move() method.
    }

    public function copy(string $source, string $destination, Config $config): void
    {
        $input = new CopyObjectInput($this->bucket, $destination, $this->bucket, $source);
        try {
            $output = $this->client->copyObject($input);
            logger()->info(__METHOD__, ['request_id' => $output->getRequestId()]);
        } catch (TosClientException $e) {
            throw $e;
        }
    }

    public function getUrl($filename)
    {
        // 判断 domain 是否 http/https 开头
        if (! preg_match('/^http(s)?:\/\//', $this->domain)) {
            $this->domain = 'http://' . $this->domain;
        }
        $domainParsed = parse_url($this->domain);

        return Arr::get($domainParsed, 'scheme', 'http') . '://' . $this->bucket . '.' . $domainParsed['host'] . '/' . $filename;
    }

    public function getTemporaryUrl(string $path, \DateTimeInterface $expiration, array $options = []): string
    {
        $input = $this->preSignedURLInput(Enum::HttpMethodGet, $path, $expiration);
        $output = $this->client->preSignedURL($input);
        // 获取预签名的 URL
        return $output->getSignedUrl();
    }

    private function preSignedURLInput(string $method, string $path, \DateTimeInterface $expiration): PreSignedURLInput
    {
        $expirationTime = $expiration->getTimestamp();
        $expires = $expirationTime - time();
        $expires = max($expires, 0);
        $expires = min($expires, 7 * 24 * 60 * 60);
        $input = new PreSignedURLInput($method, $this->bucket, $path);
        $input->setExpires($expires);

        return $input;
    }

    public function temporaryUploadUrl(string $path, \DateTimeInterface $expiration, array $options = []): array
    {
        $input = $this->preSignedURLInput(Enum::HttpMethodPut, $path, $expiration);
        //        $input = $this->preSignedURLInput(Enum::HttpMethodPost, $path, $expiration);
        $output = $this->client->preSignedURL($input);
        // 获取预签名的 URL
        return [
            'url' => $output->getSignedUrl(),
            'headers' => $output->getSignedHeader(),
        ];
    }
}
